/**
 * Copyright (C) 2008 by The Regents of the University of California
 * Redistribution of this file is permitted under the terms of the GNU
 * Public License (GPL).
 *
 * @author Junghoo "John" Cho <cho AT cs.ucla.edu>
 * @date 3/24/2008
 */

#include <cstdio>
#include <iostream>
#include <fstream>
#include "Bruinbase.h"
#include "SqlEngine.h"

using namespace std;

// external functions and variables for load file and sql command parsing 
extern FILE* sqlin;
int sqlparse(void);


RC SqlEngine::run(FILE* commandline)
{
  fprintf(stdout, "Bruinbase> ");

  // set the command line input and start parsing user input
  sqlin = commandline;
  sqlparse();  // sqlparse() is defined in SqlParser.tab.c generated from
               // SqlParser.y by bison (bison is GNU equivalent of yacc)

  return 0;
}

RC SqlEngine::select(int attr, const string& table, const vector<SelCond>& cond)
{
  RecordFile rf;   // RecordFile containing the table
  RecordId   rid;  // record cursor for table scanning
  BTreeIndex idx;	// index for the table

  bool hasNotEquals = false;
  
  RC     rc;
  int    key;     
  string value;
  int    count = 0;
  int    diff;
  int	 idxState = 0;

  // open the table file
  if ((rc = rf.open(table + ".tbl", 'r')) < 0) {
    fprintf(stderr, "Error: table %s does not exist\n", table.c_str());
    return rc;
  }
  
  SelCond* keyIs = NULL;
  SelCond* keyMax = NULL;
  SelCond* keyMin = NULL;
  SelCond* keyNe = NULL;
  
  SelCond* valIs = NULL;
  SelCond* valMax = NULL;
  SelCond* valMin = NULL;
  SelCond* valNe = NULL;
  
  int temp;
  
  int keyGe = 0;
  int keyLe = 0;
  
  for (unsigned i = 0; i < cond.size(); i++) {
	temp = atoi(cond[i].value);
	switch(cond[i].attr) {
	case 1:
		switch (cond[i].comp) {
		case SelCond::EQ:
			keyIs = (SelCond*) &cond[i];
			break;
		case SelCond::NE:
			hasNotEquals = true;
			keyNe = (SelCond*) &cond[i];
			break;
		case SelCond::LE:
			temp++;
		case SelCond::LT:
			if (keyMax == NULL || atoi(keyMax->value) < temp) {
				keyMax = (SelCond*) &cond[i];
				if(keyMax->comp == SelCond::LE) {
					keyLe = 1;
				} else {
					keyLe = 0;
				}
			}
			break;
		case SelCond::GE:
			temp--;
		case SelCond::GT:
			if (keyMin == NULL || atoi(keyMin->value) > temp) {
				keyMin = (SelCond*) &cond[i];
				if(keyMin->comp == SelCond::GE) {
					keyGe = 1;
				} else {
					keyGe = 0;
				}
			}
			break;
		}
		break;
	case 2:
		switch (cond[i].comp) {
		case SelCond::EQ:
			valIs = (SelCond*) &cond[i];
			break;
		case SelCond::NE:
			hasNotEquals = true;
			valNe = (SelCond*) &cond[i];
			break;
		case SelCond::LE:
			temp++;
		case SelCond::LT:
			if (valMax == NULL || atoi(valMax->value) < temp)
				valMax = (SelCond*) &cond[i];
			break;
		case SelCond::GE:
			temp--;
		case SelCond::GT:
			if (valMin == NULL || atoi(valMin->value) > temp)
				valMin = (SelCond*) &cond[i];
			break;
		}
		break;
	}
}
  
  if ((idx.open(table + ".idx", 'r')) == 0) {
	idxState = 1;
	
	IndexCursor ic;
	
	//lookup
	if (keyIs || keyMin || keyMax || keyNe) {
		
		int keyToFind;
		int keyLow;
		int keyHigh;
		int keyNot;
		if (keyIs)
			keyToFind = atoi(keyIs->value);
		else if(keyMin) {
			keyLow = atoi(keyMin->value);
			keyToFind = keyLow;
		}
		
		if(keyMax) {
			keyHigh = atoi(keyMax->value);
			if(! keyMin) {
				keyToFind = keyHigh;
			}
		}
		
		if(keyNe) {
			keyNot = atoi(keyNe->value);
		}

		// Location error
		if(keyMax) {
			if((rc = idx.locate(0, ic)) != 0) {
				idx.close();
				return rc;
			}
		} else if(! keyNe){
			if ((rc = idx.locate(keyToFind, ic)) != 0) {
				idx.close();
				return rc;
			}
		}
			
		while (idx.readForward(ic, key, rid) == 0 ) {		   			
			if(keyIs && key != keyToFind) continue;
			if(keyMin && ((keyGe==1)?(key < keyLow):(key <= keyLow))) continue;
			if(keyMax && ((keyLe==1)?(key > keyHigh):(key >= keyHigh))) break;
			if(keyNe && key == keyNot) continue;
			
			 if ((rc = rf.read(rid, key, value)) != 0) {
				fprintf(stderr, "Error: while reading a tuple from table %s\n", table.c_str());
				idx.close();
				return rc;
			}

			if (valIs != NULL && value != valIs->value)		continue;
			if (valMax != NULL &&  value >= valMax->value)	continue;
			if (valMin != NULL && value <= valMin->value)	continue;
			if (valNe && value <= valNe->value) continue;

			count++;

			switch (attr) {
			case 1:
			  fprintf(stdout, "%d\n", key);
			  break;
			case 2:
			  fprintf(stdout, "%s\n", value.c_str());
			  break;
			case 3:  // SELECT *
			  fprintf(stdout, "%d '%s'\n", key, value.c_str());
			  break;
			}
			
			if(keyIs && key == keyToFind) break;
			if(keyNe && key != keyNot) break;
		}
	}
	
	if(keyIs || keyMin || keyMax || keyNe) {
		if(attr == 4) {
			fprintf(stdout, "%d\n", count);
		}
	}
	
	if(! (keyIs || keyMin || keyMax)) {
		idx.close();
		goto tablescan;
	}
	
	idx.close();
  }
  
  if (!idxState) {
	
tablescan:
	rid.pid = rid.sid = 0;
	count = 0;
	while (rid < rf.endRid()) {
		if ((rc = rf.read(rid, key, value)) < 0) {
		  fprintf(stderr, "Error: while reading a tuple from table %s\n", table.c_str());
		  goto exit_select;
		}
		if (keyIs != NULL && (int) key != atoi(keyIs->value))	goto next_tuple;
		if (keyMax != NULL && (int) key >= atoi(keyMax->value))	goto next_tuple;
		if (keyMin != NULL && (int) key <= atoi(keyMin->value))	goto next_tuple;
		if (keyNe && (int) key == atoi(keyNe->value)) goto next_tuple;

		if (valIs != NULL && value != valIs->value)		goto next_tuple;
		if (valMax != NULL &&  value >= valMax->value)	goto next_tuple;
		if (valMin != NULL && value <= valMin->value)	goto next_tuple;
		if (valNe && value == valNe->value) goto next_tuple;

		count++;

		switch (attr) {
		case 1:
		  fprintf(stdout, "%d\n", key);
		  break;
		case 2:
		  fprintf(stdout, "%s\n", value.c_str());
		  break;
		case 3:
		  fprintf(stdout, "%d '%s'\n", key, value.c_str());
		  break;
	}
	next_tuple:
	++rid;
	}

	if (attr == 4) {
	fprintf(stdout, "%d\n", count);
	}
	rc = 0;

	exit_select:
	rf.close();
	}
	return rc;
}

RC SqlEngine::load(const string& table, const string& loadfile, bool index)
{
  
	// for part D
	BTreeIndex temp_index;

	// if index is true create a tblname.idx' where tblname is the name of the table
	if(index)
      temp_index.open(table + ".idx", 'w');

  // Attempt to open the file for reading
  fstream fs;
  const char *lf = loadfile.c_str();
  fs.open(lf, fstream::in);

  // Check if opening the file failed
  if (fs.fail() == true)
  {
     fs.close();
     return RC_FILE_OPEN_FAILED;
  }
  
  // Create a record file and attempt to open the table file
  RecordFile rf;
  RC rc = rf.open(table + ".tbl", 'w');

  if (rc != 0)
  {
    fs.close();
    rf.close();
    return rc;
  }

  // Read lines
  string line;
  while (fs.eof() == false)
  {
    // Use '\n' as delimiter
    getline(fs, line);

    // Check if getting the next line failed
    if (fs.fail() == true)
    {
       fs.close();
       rf.close();
       return RC_FILE_READ_FAILED;
    }

    int key;
    string value;
    RecordId rid;

    // Check if there was an error parsing the line or Check if there was an error appending
    if ((rc = parseLoadLine(line, key, value)) != 0 || (rc = rf.append(key, value, rid)) != 0)
    {
		// insert a corresponding (key, RecordId) pair to the B+tree of the table
	    if(index)
		  if(temp_index.insert(key,rid) != 0)
		    return RC_FILE_WRITE_FAILED;
       fs.close();
       rf.close();
       return rc;
    }

	
  } 

  // Close the file
  if(index)
	  temp_index.close();
  fs.close();
  rf.close();
  return 0;
}



RC SqlEngine::parseLoadLine(const string& line, int& key, string& value)
{
    const char *s;
    char        c;
    string::size_type loc;
    
    // ignore beginning white spaces
    c = *(s = line.c_str());
    while (c == ' ' || c == '\t') { c = *++s; }

    // get the integer key value
    key = atoi(s);

    // look for comma
    s = strchr(s, ',');
    if (s == NULL) { return RC_INVALID_FILE_FORMAT; }

    // ignore white spaces
    do { c = *++s; } while (c == ' ' || c == '\t');
    
    // if there is nothing left, set the value to empty string
    if (c == 0) { 
        value.erase();
        return 0;
    }

    // is the value field delimited by ' or "?
    if (c == '\'' || c == '"') {
        s++;
    } else {
        c = '\n';
    }

    // get the value string
    value.assign(s);
    loc = value.find(c, 0);
    if (loc != string::npos) { value.erase(loc); }

    return 0;
}


